/*
 * Copyright (c) 2009 Tim Mead
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 */

/**
 * Representation of a CFA incident.
 */
package com.misoneism.fire.incident
{
	import com.adobe.xml.syndication.generic.IItem;
	import com.google.maps.InfoWindowOptions;
	import com.google.maps.LatLng;
	import com.google.maps.MapMouseEvent;
	import com.google.maps.interfaces.IMap;
	import com.google.maps.overlays.Marker;
	import com.google.maps.overlays.MarkerOptions;
	import com.google.maps.styles.FillStyle;
	import com.google.maps.styles.StrokeStyle;
	
	import flash.text.StyleSheet;
	import flash.text.TextFormat;
	
	import mx.formatters.DateFormatter;
	
	public class Incident
	{
		// Defines for 'size'
		public static const SIZE_SPOT:String = "SPOT";
		public static const SIZE_SMALL:String = "SMALL";
		public static const SIZE_MEDIUM:String = "MEDIUM";
		public static const SIZE_LARGE:String = "LARGE";
		
		// Defines for 'type'
		public static const TYPE_FALSE_ALARM:String = "FALSE ALARM";
		public static const TYPE_STRUCTURE:String = "STRUCTURE";
		public static const TYPE_NON_STRUCTURE:String = "NON STRUCTURE";
		public static const TYPE_OTHER:String = "OTHER";
		public static const TYPE_INCIDENT:String = "INCIDENT";
		public static const TYPE_HAZMAT_INCIDENT:String = "HAZMAT INCIDENT";
		public static const TYPE_GRASS:String = "GRASS";
		public static const TYPE_WILD_FIRE:String = "WILD FIRE";
		public static const TYPE_RESCUE:String = "RESCUE";
		public static const TYPE_SCRUB:String = "SCRUB";
		public static const TYPE_FOREST:String = "FOREST";
		
		// Defines for 'status'
		public static const STATUS_SAFE:String = "SAFE";
		public static const STATUS_GOING:String = "GOING";
		public static const STATUS_CONTROLLED:String = "CONTROLLED";
		public static const STATUS_CONTAINED:String = "CONTAINED";
		public static const STATUS_COMPLETED:String = "COMPLETED";
		
		/** Regular expression template for parsing RSS feed. */
		protected static const TEMPLATE:RegExp = 
			/\s*<\w*>\s*Region:\s*<\/\w*>\s*(\d+)\s*<br\s*\/?>\s*<\w*>\s*Location:\s*<\/\w*>\s*([\w \-]+)(,\s*([\w \-]+))?<br\s*\/?>\s*<\w*>\s*Date\/Time:\s*<\/\w*>\s*([\d\/ :]+)<br\s*\/?>\s*<\w*>\s*Type:\s*<\/\w*>\s*([\w ]+)<br\s*\/?>\s*<\w*>\s*Status:\s*<\/\w*>\s*([\w ]+)<br\s*\/?>\s*<\w*>\s*Size:\s*<\/\w*>\s*([\w ]+)<br\s*\/?>\s*<\w*>\s*Vehicles:\s*<\/\w*>\s*(\d+)/;
		
		/** Regular expression template for parsing time/date. */
		protected static const TIMEDATE_TEMPLATE:RegExp = 
			/\s*(\d\d)\/(\d\d)\/(\d\d\d\d)\s*(\d\d)\:(\d\d)\:(\d\d)/;
		
		/** Date formatter for displaying time/date. */
		protected static var TIMEDATE_FORMAT:DateFormatter;
		
		/** Default location to append to Geocode requests. */
		protected static const DEFAULT_LOCATION:String = "victoria australia";
		
		// Line styles for incidents
		protected static const SAFE_STROKE_STYLE:StrokeStyle =
			new StrokeStyle({color: 0x4dcc33, thickness: 2});
		protected static const CONTAINED_STROKE_STYLE:StrokeStyle =
			new StrokeStyle({color: 0xffce04, thickness: 2});
		protected static const GOING_STROKE_STYLE:StrokeStyle =
			new StrokeStyle({color: 0xfe3501, thickness: 2});
		protected static const HIGHLIGHT_STROKE_STYLE:StrokeStyle =
			new StrokeStyle({color: 0x7fceff, thickness: 4});
			
		// Fill styles for incidents
		protected static const SAFE_FILL_STYLE:FillStyle =
			new FillStyle({
				color: 0x4dcc33,
				alpha: 0.8
			});
		protected static const CONTAINED_FILL_STYLE:FillStyle =
			new FillStyle({
				color: 0xffce04,
				alpha: 0.8
			});
		protected static const GOING_FILL_STYLE:FillStyle =
			new FillStyle({
				color: 0xfe3501,
				alpha: 0.8
			});
		
		// Defines for images
		[Embed(source="../images/incident-status-safe-24.png")]
		protected static const STATUS_SAFE_ICON:Class;
		[Embed(source="../images/incident-status-going-24.png")]
		protected static const STATUS_GOING_ICON:Class;
		[Embed(source="../images/incident-status-controlled-24.png")]
		protected static const STATUS_CONTROLLED_ICON:Class;
		[Embed(source="../images/incident-status-contained-24.png")]
		protected static const STATUS_CONTAINED_ICON:Class;
		[Embed(source="../images/incident-status-completed-24.png")]
		protected static const STATUS_COMPLETED_ICON:Class;
		
		[Bindable]
		public var suburb:String;
		
		[Bindable]
		public var street:String;
		
		[Bindable]
		public var size:String;
		
		[Bindable]
		public var type:String;
		
		[Bindable]
		public var status:String;
		
		[Bindable]
		public var vehicles:Number;
		
		[Bindable]
		public var region:Number;
		
		[Bindable]
		public var dateTime:Date;
		
		[Bindable]
		public var location:LatLng;
		
		protected var _marker:Marker;
		
		private var defaultStrokeStyle:StrokeStyle;
		
		public function Incident()
		{
			if (TIMEDATE_FORMAT == null)
			{
				TIMEDATE_FORMAT = new DateFormatter();
				TIMEDATE_FORMAT.formatString = "JJ:NN DD/MM/YYYY";
			}
		}
		
		/**
		 * Derive an Incident from an RSS feed item.
		 */
		public static function deriveIncident(rssItem:IItem):Incident
		{
			// Parse the RSS item into internal fields
			var o:Object = TEMPLATE.exec(rssItem.excerpt.value);
			
			// Ensure we correctly parsed the data
			if (o == null)
			{
				trace("ERROR: Unable to parse '", rssItem.excerpt.value, "' into Incident.");
				
				return null;
			}
			
			// Create a new Incident, and inject the fields
			var tempTimedate:String;
			var i:Incident = new Incident();
			i.region = Number(o[1]);
			i.suburb = String(o[2]);
			if (o[4] != undefined)
				i.street = String(o[4]);
			tempTimedate = String(o[5]);
			i.type = String(o[6]);
			i.status = String(o[7]);
			i.size = String(o[8]);
			i.vehicles = Number(o[9]);
			
			// Parse the time and date String
			o = TIMEDATE_TEMPLATE.exec(tempTimedate);
			
			// Ensure we correctly parsed the data
			if (o == null)
			{
				trace("ERROR: Unable to parse '", tempTimedate, "' into Incident Time/Date.");
				
				return null;
			}
			
			i.dateTime = new Date(Number(o[3]), Number(o[2])-1, Number(o[1]), Number(o[4]), Number(o[5]), Number(o[6]));
			
			return i;
		}
		
		/**
		 * Get an iconic representation of the size of this incident.
		 */
		public function get statusIcon():Class
		{
			switch (this.status)
			{
				case STATUS_SAFE:
					return STATUS_SAFE_ICON;
					
				case STATUS_CONTAINED:
					return STATUS_CONTAINED_ICON;
					
				case STATUS_CONTROLLED:
					return STATUS_CONTROLLED_ICON;
					
				case STATUS_GOING:
					return STATUS_GOING_ICON;
					
				case STATUS_COMPLETED:
					return STATUS_COMPLETED_ICON;
					
				default:
					return null;
			}
		}
		
		/**
		 * Get a Geocoding-compatible address that represents this incident.
		 */
		public function get geocodeAddress():String
		{
			return ((this.street == null) ? "" : this.street + " ") +
				this.suburb + " " + DEFAULT_LOCATION;
		}
		
		/**
		 * Get an address string that represents both the suburb and optional street.
		 */
		public function get combinedAddress():String
		{
			return ((this.street == null) ? "" : this.street + ", ") +
				this.suburb;
		}
		
		/**
		 * Get a human-readable description of the type.
		 */
		public function get typeDescription():String
		{
			switch (this.type)
			{
				case TYPE_FALSE_ALARM:
					return "False Alarm";
					
				case TYPE_GRASS:
					return "Grass Fire";
					
				case TYPE_HAZMAT_INCIDENT:
					return "Hazmat Incident";
					
				case TYPE_INCIDENT:
					return "Incident";
					
				case TYPE_NON_STRUCTURE:
					return "Non Structure";
					
				case TYPE_OTHER:
					return "Other";
					
				case TYPE_RESCUE:
					return "Rescue";
					
				case TYPE_STRUCTURE:
					return "Structure";
					
				case TYPE_WILD_FIRE:
					return "Wild Fire";
					
				case TYPE_SCRUB:
					return "Scrub";
					
				case TYPE_FOREST:
					return "Forest";
					
				default:
					return "Unrecognised Incident Type (" + this.type + ")";
			}
		}
		
		/**
		 * Get a human-readable description of the status.
		 */
		public function get statusDescription():String
		{
			switch (this.status)
			{
				case STATUS_CONTAINED:
					return "Contained";
					
				case STATUS_CONTROLLED:
					return "Controlled";
					
				case STATUS_COMPLETED:
					return "Completed";
					
				case STATUS_GOING:
					return "Going";
					
				case STATUS_SAFE:
					return "Safe";
					
				default:
					return "Unrecognised Incident Status (" + this.status + ")";
			}
		}
		
		/**
		 * Get a human-readable description of the size.
		 */
		public function get sizeDescription():String
		{
			switch (this.size)
			{
				case SIZE_LARGE:
					return "Large";
					
				case SIZE_MEDIUM:
					return "Medium";
					
				case SIZE_SMALL:
					return "Small";
					
				case SIZE_SPOT:
					return "Spot";
					
				default:
					return "Unknown Incident Size (" + this.size + ")";
			}
		}
		
		/**
		 * Get a human-readable description of the region.
		 */
		public function get regionDescription():String
		{
			// TODO: Fix this
			return String(region);
		}
		
		/**
		 * Get a simple tooltip representing this incident.
		 */
		public function get simpleTooltip():String
		{
			return this.typeDescription + " - " + this.statusDescription + " (" +
				this.sizeDescription + ")";
		}
		
		/**
		 * Get a HTML description representing this incident, excluding the address.
		 */
		public function get simpleHtmlDescription():String
		{
			return "<b>Type:</b> " + typeDescription +
				"<br/><b>Status:</b> " + statusDescription +
				"<br/><b>Size:</b> " + sizeDescription +
				"<br/><b>Time/date:</b> " + TIMEDATE_FORMAT.format(dateTime) +
				"<br/><b>Vehicles:</b> " + vehicles +
				"<br/><b>Region:</b> " + regionDescription;
		}
		
		/**
		 * Get a HTML description representing this incident, including the address.
		 */
		public function get fullHtmlDescription():String
		{
			return "<b>" + combinedAddress + "</b><br/>" + simpleHtmlDescription;
		}
		
		/**
		 * Get a Marker object that represents this Incident.
		 */
		public function get marker():Marker
		{
			// If we've computed a marker before, then return that
			if (this._marker != null)
				return this._marker;
				
			// If we don't have a location, then we can't have a marker
			if (this.location == null)
				return null;
			
			var radius:Number;
			var strokeStyle:StrokeStyle;
			var fillStyle:FillStyle;
			var labelFormat:TextFormat = new TextFormat();
			
			// Determine the size of the marker
			switch (this.size)
			{
				case SIZE_LARGE:
					radius = 16;
					break;
				
				case SIZE_MEDIUM:
					radius = 12;
					break;
					
				case SIZE_SMALL:
					radius = 8;
					break;
					
				case SIZE_SPOT:
				default:
					radius = 4;
					break;
			}
			
			// Determine the colour of the marker
			switch (this.status)
			{
				case STATUS_SAFE:
				case STATUS_COMPLETED:
					defaultStrokeStyle = strokeStyle = SAFE_STROKE_STYLE;
					fillStyle = SAFE_FILL_STYLE;
					break;
					
				case STATUS_CONTAINED:
				case STATUS_CONTROLLED:
					defaultStrokeStyle = strokeStyle = CONTAINED_STROKE_STYLE;
					fillStyle = CONTAINED_FILL_STYLE;
					break;
					
				case STATUS_GOING:
					defaultStrokeStyle = strokeStyle = GOING_STROKE_STYLE;
					fillStyle = GOING_FILL_STYLE;
					break;
			}
			
			var markerOptions:MarkerOptions = new MarkerOptions({
				strokeStyle: strokeStyle,
				fillStyle: fillStyle,
				label: "",
				labelFormat: labelFormat,
				tooltip: this.simpleTooltip,
				radius: radius,
				hasShadow: false,
				clickable: true,
				draggable: false,
				gravity: 0.5,
				distanceScaling: false
			});
			
			var marker:Marker = new Marker(this.location, markerOptions);
			
			marker.addEventListener(MapMouseEvent.CLICK,
				function(event:MapMouseEvent):void
				{
					var map:IMap = event.feature.pane.map as IMap;
					var marker:Marker = event.feature as Marker;
					
					map.closeInfoWindow();
					
					var contentStyleSheet:StyleSheet = new StyleSheet();
					contentStyleSheet.setStyle("p", { fontFamily: "_sans" });
					
					marker.openInfoWindow(new InfoWindowOptions({
						contentHTML: simpleHtmlDescription,
						contentStyleSheet: contentStyleSheet,
						hasShadow: false,
						hasCloseButton: true,
						title: combinedAddress,
						titleFormat: new TextFormat("Myriad Web", 14)
					}));
				});
				
			this._marker = marker;
			
			return this._marker;
		}
		
		/**
		 * Display the highlight style on this marker.
		 */
		public function showHighlight():void
		{
			// We can't do anything if a marker hasn't been generated yet
			if (_marker == null)
				return;
				
			_marker.setOptions(new MarkerOptions({strokeStyle: HIGHLIGHT_STROKE_STYLE}));
		}
		
		/**
		 * Remove the highlight style from this marker.
		 */
		public function removeHighlight():void
		{
			// We can't do anything if a marker hasn't been generated yet
			if (_marker == null)
				return;
				
			_marker.setOptions(new MarkerOptions({strokeStyle: defaultStrokeStyle}));
		}
	}
}